package gameoflive;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Game implements Runnable, MouseMotionListener
{
    private static Game instance;
    
    public synchronized static Game getInstance()
    {
        if (instance == null)
        {
            instance = new Game();
        }
        return instance;
    }
    
    public static synchronized void log(Object message, boolean newLine)
    {
        if (message instanceof Throwable)
        {
            System.out.println(((Throwable) message).getLocalizedMessage());
            ((Throwable) message).printStackTrace(System.out);
        }
        else
        {
            System.out.print(message.toString());
        }
        if (newLine)
        {
            System.out.println();
        }
    }
    
    public static synchronized void log(Object message)
    {
        log(message, true);
    }
    
    public final int WINDOW_WIDTH  = 1500;
    public final int WINDOW_HEIGHT = 800;

    private JFrame   frame;
    private JPanel   panel;
    private int      width;
    private int      height;
    
    private boolean  fullscreen    = false;
    
    private Game()
    {
        // panel
        panel = new JPanel()
        {
            private static final long serialVersionUID = -1923410016305041430L;

            @Override
            protected void paintComponent(Graphics g)
            {
                render(g);
            }
        };
        panel.setLayout(null);
        panel.setIgnoreRepaint(true);
        // frame
        frame = new JFrame("Basic Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Rectangle size =  GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
        if (fullscreen)
        {
            frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
            width = size.width;
            height = size.height;
        }
        else
        {
            width = size.width - 100;
            height = size.height - 100;
        }
        panel.setPreferredSize(new Dimension(width, height));
        panel.setBounds(0, 0, width, height);
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);
        frame.setResizable(false);
        // listener
        panel.addKeyListener(new KeyboardControl());
        panel.addMouseListener(new MouseControl());
        panel.addMouseMotionListener(this);
        panel.requestFocus();
    }
    
    public int getWidth()
    {
        return width;
    }
    
    public int getHeight()
    {
        return height;
    }
    
    public void setTitle(String title)
    {
        frame.setTitle(title);
    }
    
    private static boolean IGNORE_MOUSE = false;
    
    private static boolean CREATE_LIVE_MODE = false;
    
    @Override
    public void mouseDragged(MouseEvent e)
    {
        System.out.println(e);
    }
    
    @Override
    public void mouseMoved(MouseEvent e)
    {
        if (CREATE_LIVE_MODE)
        {
            try
            {
                Point pos = calcCoord(e.getPoint());
                Cell cell = GameOfLive.getInstance().getCell(pos.getX(), pos.getY());
                cell.setSurviving(true);
            }
            catch(IllegalArgumentException ex)
            {
                // ignore
            }
        }
    }
    
    private class MouseControl extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            if (SwingUtilities.isRightMouseButton(e))
            {
                CREATE_LIVE_MODE = CREATE_LIVE_MODE == false;
                log("CREATIVE_LIVE_MODE " + CREATE_LIVE_MODE);
            }
            else if (SwingUtilities.isLeftMouseButton(e))
            {
                if (IGNORE_MOUSE == false)
                {
                    IGNORE_MOUSE = true;
                    Point pos = calcCoord(e.getPoint());
                    Cell cell = GameOfLive.getInstance().getCell(pos.getX(), pos.getY());
                    cell.setSurviving(true);
                    IGNORE_MOUSE = false;
                }
            }
        }
    }
    
    private int       stepsFPS   = 5;
    private int       desiredFPS = 20;
    
    private final int MAX_FPS    = 120;
    private final int MIN_FPS    = 1;
    
    private static boolean IGNORE_KEYBOARD       = false;
    
    private class KeyboardControl extends KeyAdapter
    {
        @Override
        public void keyPressed(KeyEvent e)
        {
            if (IGNORE_KEYBOARD)
            {
                e.consume();
            }
            else
            {
                IGNORE_KEYBOARD = true;
                final GameOfLive gol = GameOfLive.getInstance();
                final Console console = Console.getInstance();
                if (console.consoleKeyPressed(e.getKeyCode()))
                {
                    if (console.isOpen())
                    {
                        console.closeConsole();
                    }
                    else
                    {
                        console.openConsole();
                    }
                    log("CONSOLE OPEN: " + console.isOpen());
                }
                if (console.isOpen())
                {
                    console.fireKeyEvent(e);
                }
                else
                {
                    short cellSize = 0;
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_MINUS:
                        {
                            if (desiredFPS <= 10)
                            {
                                desiredFPS--;
                            }
                            else
                            {
                                desiredFPS -= stepsFPS;
                            }
                            if (desiredFPS < MIN_FPS)
                            {
                                desiredFPS = MIN_FPS;
                            }
                            log("-FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_PLUS:
                        {
                            if (desiredFPS < 10)
                            {
                                desiredFPS++;
                            }
                            else
                            {
                                desiredFPS += stepsFPS;
                            }
                            if (desiredFPS > MAX_FPS)
                            {
                                desiredFPS = MAX_FPS;
                            }
                            log("+FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_P:
                        {
                            if (gol.isRunning())
                            {
                                if (gol.isPaused())
                                {
                                    gol.resume();
                                }
                                else
                                {
                                    gol.pause();
                                }
                            }
                            break;
                        }
                        case KeyEvent.VK_S:
                        {
                            gol.requestStop();
                            break;
                        }
                        case KeyEvent.VK_R:
                        {
                            gol.requestRestart();
                            break;
                        }
                        case KeyEvent.VK_1:
                        {
                            cellSize = 1;
                            break;
                        }
                        case KeyEvent.VK_2:
                        {
                            cellSize = 2;
                            break;
                        }
                        case KeyEvent.VK_3:
                        {
                            cellSize = 3;
                            break;
                        }
                        case KeyEvent.VK_4:
                        {
                            cellSize = 4;
                            break;
                        }
                        case KeyEvent.VK_5:
                        {
                            cellSize = 5;
                            break;
                        }
                        case KeyEvent.VK_6:
                        {
                            cellSize = 6;
                            break;
                        }
                        case KeyEvent.VK_7:
                        {
                            cellSize = 7;
                            break;
                        }
                        case KeyEvent.VK_8:
                        {
                            cellSize = 8;
                            break;
                        }
                        case KeyEvent.VK_9:
                        {
                            cellSize = 9;
                            break;
                        }
                        case KeyEvent.VK_0:
                        {
                            cellSize = 10;
                            break;
                        }
                    }
                    if (cellSize > 0 && cellSize != Game.this.cellSize)
                    {
                        log("cellSize: " + (Game.this.cellSize = cellSize));
                        Point size = calcSize();
                        gol.requestRestart(size);
                    }
                }
                IGNORE_KEYBOARD = false;
            }
        }
    }
    
    private int recalcDesiredDeltaLoop()
    {
        return (1000 * 1000 * 1000) / desiredFPS;
    }
    
    public void run()
    {
        GameOfLive.getInstance().requestRestart(calcSize());
        boolean running = true;
        while (running )
        {
            try
            {
                long beginLoopTime = System.nanoTime();
                SwingUtilities.invokeAndWait(new Runnable()
                {
                    
                    @Override
                    public void run()
                    {
                        panel.repaint();
                    }
                });
                update();
                long endLoopTime = System.nanoTime();
                long deltaLoop = endLoopTime - beginLoopTime;
                long desiredDeltaLoop = recalcDesiredDeltaLoop();
                if (deltaLoop <= desiredDeltaLoop )
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                log(sw.toString());
                running = false;
            }
        }
    }
    
    private short cellSize = 2;
    
    private Point calcSize()
    {
        int width = this.width;
        if (this.width % cellSize != 0)
        {
            width -= this.width % cellSize;
        }
        width /= cellSize;
        int height = this.height;
        if (this.height % cellSize != 0)
        {
            height -= this.height % cellSize;
        }
        height /= cellSize;
        return new Point(width, height);
    }
    
    private Point calcCoord(java.awt.Point p)
    {
        if (p.getX() > width)
        {
            throw new IllegalArgumentException(p.getX() + " > " + width);
        }
        if (p.getY() > height)
        {
            throw new IllegalArgumentException(p.getY() + " > " + height);
        }
        int x = (int) (p.getX() / cellSize);
        int y = (int) (p.getY() / cellSize);
        return new Point(x, y);
    }
    
    /**
     * Rewrite this method for your game
     */
    private void update()
    {
        GameOfLive gol = GameOfLive.getInstance();
        gol.update();
        // title
        StringBuilder title = new StringBuilder(String.valueOf(desiredFPS)).append(" FPS; ");
        title.append(gol.getStatus());
        setTitle(title.toString());
    }
    
    /**
     * Rewrite this method for your game
     */
    private void render(Graphics g)
    {
        GameOfLive gol = GameOfLive.getInstance();
        gol.render(g, cellSize);
        Console con = Console.getInstance();
        if (con.isOpen())
        {
            con.render(g, cellSize);
        }
    }
    
    private void clearScreen(Graphics2D g, int width, int height)
    {
        if (width > this.width)
        {
            width = this.width;
        }
        if (height > this.height)
        {
            height = this.height;
        }
        Color color = g.getColor();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.setColor(color);
    }
    
    private void renderCenterText(Graphics g, String s, int fontSize)
    {
        g.setColor(Color.BLACK);
        g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
        int w2 = g.getFontMetrics().stringWidth(s) / 2;
        int h2 = g.getFontMetrics().getDescent();
        g.drawString(s, width / 2 - w2, height / 2 + h2);
    }
    
    public static void main(String[] args)
    {
         Thread t = new Thread(Game.getInstance(), "Game");
         t.start();
    }
    
}
